﻿using System;
using System.Linq;
using System.Reflection;

namespace Creventive.Wpf.ShellFactory.Services
{
    internal class InjectingServiceFactory : IServiceFactory
    {
        private InstantiationMode instantiationMode;
        private ConstructorInfo constructor;
        private Type[] argumentTypes;
        private object instance;

        public InjectingServiceFactory(Type type, InstantiationMode instantiationMode)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).Where(c => c.GetCustomAttributes(typeof(ServiceConstructorAttribute), false).Any());
            switch (constructors.Count())
            {
                case 0:
                    constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
                    if (constructors.Count() == 1)
                    {
                        this.constructor = constructors.Single();
                        this.argumentTypes = this.constructor.GetParameters().Select(p => p.ParameterType).ToArray();
                    }
                    else
                    {
                        this.constructor = type.GetConstructor(Type.EmptyTypes);
                        this.argumentTypes = Type.EmptyTypes;
                    }
                    break;

                case 1:
                    this.constructor = constructors.Single();
                    this.argumentTypes = this.constructor.GetParameters().Select(p => p.ParameterType).ToArray();
                    break;

                default:
                    throw new ArgumentException(@"The type " + type.FullName + @" has multiple constructors adorned with " + typeof(ServiceConstructorAttribute).FullName + @".", "type");
            }

            if (this.constructor == null)
                throw new ArgumentException(@"The type " + type.FullName + @" has no valid constructor.", "type");
            this.instantiationMode = instantiationMode;
        }

        public object Create(IServiceContainer serviceContainer)
        {
            if (this.instance != null)
                return this.instance;

            var arguments = this.argumentTypes.Select(serviceContainer.GetService).ToArray();
            var instance = this.constructor.Invoke(arguments);
            if (this.instantiationMode == InstantiationMode.Singleton)
                this.instance = instance;

            return instance;
        }
    }
}